from typing import List

from llvmlite import ir

from .base import PMBuiltinFunction, get_function_cls
from ..builtin_func_types import builtins


class PMZeroIntArray(PMBuiltinFunction):
    def __init__(self):
        super(PMZeroIntArray, self).__init__('zero_ints')

    def __call__(self, builder, args: List = None, arg_types=None, name=''):
        from ...types import PMArrayType, integer
        assert isinstance(args, list)
        length = args[0]
        assert length.type == integer
        typ = PMArrayType(integer)
        return typ.create_by_var(builder, length, name), typ


class PMZeroArray(PMBuiltinFunction):
    def __init__(self):
        super(PMZeroArray, self).__init__('zero_array')

    def __call__(self, builder, args: List = None, arg_types=None, name=''):
        from ...types import integer
        assert isinstance(args, list)
        assert len(args) == 2
        length = args[1]
        typ = arg_types[0]
        assert length.type == integer
        return typ.create_by_var(builder, length, name), typ


class PMResizeArray(PMBuiltinFunction):
    def __init__(self):
        super(PMResizeArray, self).__init__('resize_array')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMArrayType, integer

        assert isinstance(args, list)
        assert isinstance(arg_types, list)
        assert len(args) == 3 == len(arg_types)
        array_ptr = args[0]
        length = args[1]
        last_length = args[2]
        assert array_ptr
        assert length.type == integer
        assert last_length.type == integer
        array_type: PMArrayType = arg_types[0]

        array_data_pointer = array_type.get_ptr(builder, array_ptr, 'array_pointer')
        realloc_arguments = [builder.bitcast(array_data_pointer, ir.PointerType(ir.IntType(8))), length, last_length]
        reallocated = builtins.realloc(builder, realloc_arguments, name=name)
        bitcasted = builder.bitcast(reallocated, array_type.build_structure())
        array_type.reset_ptr(builder, array_ptr, bitcasted)
        array_type.set_length(builder, array_ptr, length)
        return reallocated, arg_types[0]


class PMArrayGetLength(PMBuiltinFunction):

    def __init__(self):
        super().__init__('len')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMArrayType, integer
        assert len(args) == len(arg_types) == 1
        ref = args[0]
        assert isinstance(arg_types[0], PMArrayType)
        return arg_types[0].get_length(builder, ref, name), arg_types[0]


class PMIntArrayFromBuffer(PMBuiltinFunction):

    def __init__(self):
        super().__init__('from_buffer_int')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMBasicType, integer, PMArrayType
        assert len(args) == len(arg_types) == 2
        assert isinstance(arg_types[0], PMBasicType) and arg_types[0].type == integer
        created, typ = PMArrayType.from_buffer(builder, args[0], args[1], 'integer', name)
        return created, typ


class PMDoubleArrayFromBuffer(PMBuiltinFunction):

    def __init__(self):
        super().__init__('from_buffer_double')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMBasicType, double, PMArrayType
        assert len(args) == len(arg_types) == 2
        assert isinstance(arg_types[0], PMBasicType) and arg_types[0].type == double
        created, typ = PMArrayType.from_buffer(builder, args[0], args[1], 'double', name)
        return created, typ


class PMCreateIntegerSet(PMBuiltinFunction):

    def __init__(self):
        super().__init__('create_integer_set')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMIntegerSetType
        assert len(args) == len(arg_types) == 0
        created = PMIntegerSetType().create(builder, name)
        return created, PMIntegerSetType()


class PMAddToIntegerSet(PMBuiltinFunction):

    def __init__(self):
        super().__init__('add_to_integer_set')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMIntegerSetType, PMBasicType
        assert len(args) == len(arg_types) == 2
        assert isinstance(arg_types[0], PMIntegerSetType)
        assert isinstance(arg_types[1], PMBasicType)
        builtins.add_to_integer_set(builder, PMIntegerSetType().get_body_ptr(builder, args[0]), args[1])


class PMShowIntegerSetElements(PMBuiltinFunction):

    def __init__(self):
        super().__init__('show_integer_set_elements')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMIntegerSetType
        assert len(args) == len(arg_types) == 1
        assert isinstance(arg_types[0], PMIntegerSetType)

        builtins.show_integer_set_elements(builder, PMIntegerSetType().get_body_ptr(builder, args[0]))


class PMIntegerSetHasElement(PMBuiltinFunction):

    def __init__(self):
        super().__init__('integer_set_has')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMIntegerSetType, PMBasicType, integer
        assert len(args) == len(arg_types) == 2
        assert isinstance(arg_types[0], PMIntegerSetType)
        assert isinstance(arg_types[1], PMBasicType)
        ret = builtins.integer_set_has(builder, PMIntegerSetType().get_body_ptr(builder, args[0]), args[1])
        return ret, PMBasicType(integer)


class PMIntegerSetToBuffer(PMBuiltinFunction):

    def __init__(self):
        super().__init__('integer_set_to_buffer')

    def __call__(self, builder: ir.IRBuilder, args: List = None, arg_types: List = None, name=''):
        from ...types import PMIntegerSetType, PMBasicType, integer, PMArrayType
        assert len(args) == len(arg_types) == 2
        assert isinstance(arg_types[0], PMIntegerSetType)
        assert isinstance(arg_types[1], PMBasicType)
        assert arg_types[1].type == integer
        ptr = builtins.integer_set_to_buffer(builder, PMIntegerSetType().get_body_ptr(builder, args[0]), )
        array_type = PMArrayType(integer)
        return array_type.from_buffer(builder, builder.ptrtoint(ptr, integer), args[1], 'integer', name)


container_func_interfaces = get_function_cls(globals())
